<?
class Form
{
	// <FORM> tag params
	var $name;
	var $action;
	var $method="post";
	var $title;
	// template for rendering
	var $template="form";
	// form object's stotages
	var $controls;
	var $validators;
	var $conditions;
	var $data_source;
	// table to get data from
	var $table;
	// the field to be used as ID and it's current value
	var $id_mode="single"; // or "hiddens"
	var $id_field="id";
	var $id;
	// parameters that should be saved in form
	var $query_string;
	// state of the form
	var $isEdit=false;
	var $isUpdate=false;
	// do we need positioning
	var $hasPos=false;
	var $posKey=""; // дополнительный параметр, отвечающий за упорядочивание внутри "табов"
	// which events should we handle
	var $doAdd=true;
	var $doEdit=true;
	var $doDelete=true;
	var $doUpdate=true;
	var $doMoveup=true;
	var $doMovedown=true;
	// triggers - function calls for EVAL after specific actions
	var $afterAdd;
	var $afterUpdate;
	// javascript front-end params
	var $cancelAction='showlist()';
	var $error_message;
	
	
	// constructor
	function Form($name="form",$action="")
	{
		$controls=Array();
		$validators=Array();
		$this->name=$name;
		$this->action=$action;
		
		$this->id = intval( $_REQUEST[$this->id_field] ) ;
		
		$this->isEdit = ($_REQUEST["formaction"]=="edit");
		$this->isUpdate = ($_REQUEST["formaction"]=="update");
		
//		$this->query_string=LinkBuilder("formaction=&id=&pager=".$_REQUEST["pager"]."&sortby=".$_REQUEST["sortby"]."&sortdir=".$_REQUEST["sortdir"]);
		$this->query_string=LinkBuilder("formaction=&id=");
	}
	
	// add object to the form
	function Add($obj, $editOrUpdateOnly=false)
	{
		if(!$editOrUpdateOnly || ($editOrUpdateOnly && ($this->isEdit || $this->isUpdate)))
		{
			if($obj->isValidator)
			{
				$newvalidator = $obj;
				$newvalidator->isUpdate=$this->isUpdate;
				if(!is_array($this->validators[$obj->name])) $this->validators[$obj->name]= Array();
				$this->validators[$obj->name][] = $newvalidator;
			}
			else
			{
				$obj->table=$this->table;
				$this->controls[$obj->name]=$obj;
			}
		}
	}
	
	// bind data from either data-source or $_Request to the controls
	function DataBind()
	{
		foreach($this->controls as $k=>$control)
		{
			if($this->controls[$k]->value=="") $this->controls[$k]->value = $this->data_source[ $control->name ];
			if($this->controls[$k]->value=="") $this->controls[$k]->value = $_REQUEST[ $control->name ];
		}
	}
	
	// handle all form events: add edit update delete etc.
	function Handle()
	{
		$act = ucfirst(strtolower($_REQUEST["formaction"]));
		if($act) eval('if($this->do'.$act.') $this->On'.$act.'();');
		else if($this->isEdit) $this->OnEdit();
	}
	
	// process all form inner engines and generate HTML-code of the form
	function Render()
	{
		$this->Handle();
		$this->DataBind();
		
		$script = $this->EvaluateConditions();
		
		$ctrls = Array();
		foreach($this->controls as $k=>$control)
		{
			if( $control->enctype ) $enctype = 'enctype="multipart/form-data"';
			
			if( GetClass($control) == "hidden")
				$hiddens.=$this->controls[$k]->Render();
			else
			{
				$temp = Array();
				$temp["title"] = $control->title;
				$temp["name"] = $control->name;
				$temp["rowid"] = $this->name."_".$control->name;
				$temp["visible"] = $control->visible;
				$temp["code"] = $this->controls[$k]->Render();
				if($this->validators[$control->name])
				$temp["message"] = $this->validators[$control->name]->message;
				$temp["controltype"] = GetClass($control);
				$ctrls[] = $temp;
			}
		}
		if($this->isEdit || $this->isUpdate)
		{
			$buttons.=
				'<input type="hidden" name="formaction"  value="update">'.
				'<input type="hidden" name="id"  value="'.$this->data_source["id"].'">'.
/*				'<input type="hidden" name="pager"  value="'.$_REQUEST["pager"].'">'.
				'<input type="hidden" name="sortdir"  value="'.$_REQUEST["sortdir"].'">'.
				'<input type="hidden" name="sortby"  value="'.$_REQUEST["sortby"].'">'.*/
				
				'<button type="submit"><img src="images/save.gif" alt="">Сохранить</button>'.
				'<button type="reset" onclick="'.$this->cancelAction.'"><img src="images/cancel.gif" alt="">Отменить</button>';
		}
		else
		{
			$buttons.='<input type="hidden" name="formaction"  value="add">'.
				 '<button type="submit"><img src="images/add.gif" alt="">Добавить</button>'.
  			     '<button type="reset" onclick="'.$this->cancelAction.'"><img src="images/cancel.gif" alt="">Отменить</button>';
		}
		
		
		$dao = Array();
		$dao["name"] = $this->name;
		$dao["action"] = $this->action;
		$dao["method"] = $this->method;
		$dao["hidden_fields"] = $hiddens;
		$dao["controls"] = $ctrls;
		$dao["buttons"] = $buttons;
		$dao["script"] = $script;
		$dao["enctype"] = $enctype;
		$dao["error_message"] = $this->error_message;
		$dao["formtitle"] = ($this->title?$this->title:GetAdminTitle());
		
		return TemplaterParse(GetAdminTemplate($this->template),$dao);
	}
	
	
	
	
	
	
	
	// handlers:
	function OnMoveUp()
	{
		$pos=0;
		$newpos=0;
		$newid=0;
		
		if($this->posKey=="")
			$pos=ScalarSelect("select pos from $this->table where $this->id_field='$this->id'");
		else
			list($pos,$key) = DBSingleRow("select pos,".$this->posKey." from $this->table where $this->id_field='$this->id'");
		
		// при поиске элемента, с которым будем меняться местами,
		// надо учитывать чтобы у него совпадали скрытые параметры формы,
		// для того чтобы перемещение происходило лишь в рамках текущей группы
		
		foreach($this->controls as $control)
		{
			if(GetClass($control) == "hidden")
			$extra_sql.=" and ".$control->name."='$control->value'";
		}
		if($this->posKey) $extra_sql.=" and ".$this->posKey."='$key'";
		
		$query = "select $this->id_field,pos from $this->table where pos<$pos $extra_sql order by pos desc limit 1";
		if($row = mysql_fetch_array(	mysql_query($query) ))
		{
			$newpos=$row["pos"];
			$newid=$row[$this->id_field];
		}
		
		if($pos && $newid)
		{
			//$query = "update $this->table set pos=$newpos where $this->id_field=$this->id";
			//mysql_query($query);
			DBUpdate($this->table,Array('pos'),Array('pos'=>$newpos,'id'=>$this->id));
			
			//$query = "update $this->table set pos=$pos where $this->id_field=$newid";
			//mysql_query($query);
			DBUpdate($this->table,Array('pos'),Array('pos'=>$pos,'id'=>$newid));
		}
		ClearGet($this->query_string);
	}
	
	function OnMoveDown()
	{
		$pos=0;
		$newpos=0;
		$newid=0;
		
		if($this->posKey=="")
			$pos=ScalarSelect("select pos from $this->table where $this->id_field='$this->id'");
		else
			list($pos,$key) = DBSingleRow("select pos,".$this->posKey." from $this->table where $this->id_field='$this->id'");
		
		// при поиске элемента, с которым будем меняться местами,
		// надо учитывать чтобы у него совпадали скрытые параметры формы,
		// для того чтобы перемещение происходило лишь в рамках текущей группы
		
		foreach($this->controls as $control)
		{
			if(GetClass($control) == "hidden")
			$extra_sql.=" and ".$control->name."='$control->value'";
		}
		if($this->posKey) $extra_sql.=" and ".$this->posKey."='$key'";
		
		$query = "select $this->id_field,pos from $this->table where pos>$pos $extra_sql order by pos limit 1";
		if($row = mysql_fetch_array(	mysql_query($query) ))
		{
			$newpos=$row["pos"];
			$newid=$row[$this->id_field];
		}
		
		if($pos && $newid)
		{
//			$query = "update $this->table set pos=$newpos where $this->id_field=$this->id";
//			mysql_query($query);
			DBUpdate($this->table,Array('pos'),Array('pos'=>$newpos,'id'=>$this->id));
			
//			$query = "update $this->table set pos=$pos where $this->id_field=$newid";
//			mysql_query($query);
			DBUpdate($this->table,Array('pos'),Array('pos'=>$pos,'id'=>$newid));
		}
		
		ClearGet($this->query_string);
	}
	
	
	
	function OnEdit()
	{
		if(!$this->table) return; // уже есть данные
		if($this->id_mode=="single")
		{
			$query = "select * from ".$this->table." where $this->id_field='$this->id'";
		}
		if($this->id_mode=="hiddens")
		{
			foreach($this->controls as $control)
			{
				if(GetClass($control) == "hidden")
				{
					if($extra_sql) $extra_sql.=" and ";
					$extra_sql.=$control->name."='".mysql_real_escape_string($control->value)."'";
				}
			}
			
			$query = "select * from $this->table where $extra_sql";
		}
		$res = mysql_query($query);
		if(mysql_num_rows($res)>0)
			$this->data_source = mysql_fetch_array($res);
	}
	
	
	function OnAdd()
	{
		if(! $this->Validate() ) return;
		$first=true;
		
		$columns2=Array();
		$values2=Array();
		
		foreach($this->controls as $k=>$control)
		{
			if($control->handleBeforeAdd) $this->controls[$k]->OnBeforeAdd($new_id);
		}
		
		foreach($this->controls as $control)
		{
			if(!$control->skipSql)
			{
				if(!$first)
				{
					$columns.=",";
					$values.=",";
				}
				else $first=false;
				
				$columns.=$control->name;
				$columns2[]=$control->name;
				
				switch(GetClass($control))
				{
					case "data":
						$values.="'".strtotime($_REQUEST[$control->name."_year"]."-".$_REQUEST[$control->name."_month"]."-".$_REQUEST[$control->name."_day"])."'";
						$values2[$control->name] = strtotime($_REQUEST[$control->name."_year"]."-".$_REQUEST[$control->name."_month"]."-".$_REQUEST[$control->name."_day"]);
						break;
					case "dataext":
						$values.="'".strtotime($_REQUEST[$control->name."_year"]."-".$_REQUEST[$control->name."_month"]."-".$_REQUEST[$control->name."_day"]." ".$_REQUEST[$control->name."_hour"].":".$_REQUEST[$control->name."_minute"])."'";
						$values2[$control->name] = strtotime($_REQUEST[$control->name."_year"]."-".$_REQUEST[$control->name."_month"]."-".$_REQUEST[$control->name."_day"]." ".$_REQUEST[$control->name."_hour"].":".$_REQUEST[$control->name."_minute"]);
						break;
					case "checkbox":
						$values.=($_REQUEST[$control->name]?1:0);
						$values2[$control->name] = ($_REQUEST[$control->name]?1:0);
						break;
					case "userfile":
					case "picture":
						$values.="'".mysql_real_escape_string($control->value)."'";
						$values2[$control->name] = $control->value;
						break;
					default:
						$values.="'".mysql_real_escape_string($_REQUEST[$control->name])."'";
						$values2[$control->name] = $_REQUEST[$control->name];
				}
			}
		}
		
		$pos=1;
		if($this->hasPos)
		{
			$res = mysql_query("select pos from $this->table order by pos desc limit 1");
			if($row=mysql_fetch_array($res))
			{
				$pos=$row[0]+1;
			}
			
			$columns.= ",pos";
			$values .= ",$pos";
			
			$columns2[]='pos';
			$values2['pos'] = $pos;
		}
		
		//$query = "insert into $this->table ($columns) values ($values)";
		//if(!mysql_query($query)) {$this->error_message=mysql_error();$GLOBALS["validation"]="failed";return;}
		//$new_id = mysql_insert_id();
		$new_id = DBInsert($this->table , $columns2, $values2);
		
		foreach($this->controls as $k=>$control)
		{
			if($control->handleAdd) $this->controls[$k]->OnAdd($new_id);
		}
		
		if($this->afterAdd) eval($this->afterAdd);
		
		ClearGet($this->query_string);
	}
	
	
	function OnUpdate()
	{
		if(! $this->Validate() ) return;
		if( $_REQUEST[$this->id_field] == ""  ) return $this->OnAdd();
		
		// loading old-data for very special reasons like fields 'filename'
		$this->OnEdit();
		$this->DataBind();
		
		foreach($this->controls as $k=>$control)
		{
			if($control->handleBeforeUpdate) $this->controls[$k]->OnBeforeUpdate($this->id);
		}
		
		$first=true;
		foreach($this->controls as $control)
		{
			if(!$control->skipSql)
			{
				if(!$first)
				{
					$columns.=",";
				}
				else $first=false;
				
				switch(GetClass($control))
				{
					case "data":
						$columns.=$control->name."='".strtotime($_REQUEST[$control->name."_year"]."-".$_REQUEST[$control->name."_month"]."-".$_REQUEST[$control->name."_day"])."'";
						break;
					case "dataext":
						$columns.=$control->name."='".strtotime($_REQUEST[$control->name."_year"]."-".$_REQUEST[$control->name."_month"]."-".$_REQUEST[$control->name."_day"]." ".$_REQUEST[$control->name."_hour"].":".$_REQUEST[$control->name."_minute"])."'";
						break;
					case "checkbox":
						$columns.=$control->name."='".($_REQUEST[$control->name]?1:0)."'";
						break;
					case "userfile":
					case "picture":
						$columns.=$control->name."='".mysql_real_escape_string($control->value)."'";
						break;
					default:
						$columns.=$control->name."='".mysql_real_escape_string($_REQUEST[$control->name])."'";
				}
			}
		}
		
		
		if($this->id_mode=="single")
		{
			$query = "update $this->table set $columns where $this->id_field='$this->id'";
		}
		if($this->id_mode=="hiddens")
		{
			foreach($this->controls as $control)
			{
				if(GetClass($control) == "hidden")
				{
					if($extra_sql) $extra_sql.=" and ";
					$extra_sql.=$control->name."='$control->value'";
				}
			}
			
			$query = "update $this->table set $columns where $extra_sql";
		}
		
		DBClearTableCache($this->table);
		if(!mysql_query($query)) {$this->error_message=mysql_error();$GLOBALS["validation"]="failed";return;}
		
		foreach($this->controls as $k=>$control)
		{
			if($control->handleUpdate) $this->controls[$k]->OnUpdate($_REQUEST[id]);
		}
		
		if($this->afterUpdate) eval($this->afterUpdate);
		
		ClearGet($this->query_string);
	}
	
	
	function OnDelete()
	{
		$ids = explode(',',$_REQUEST[id]);
		foreach($ids as $iii)
		{
			$i=intval($iii);
			foreach($this->controls as $k=>$control)
			{
				if($control->handleDelete) $this->controls[$k]->OnDelete($i);
			}
			
			DBDelete($this->table,"id='$i'");
		}
		ClearGet($this->query_string);
	}
	
	
	// execute validators for the controls
	function Validate()
	{
		$res = true;
		foreach($this->controls as $control)
		{
			if(is_array($this->validators[$control->name]))
			{
				foreach($this->validators[$control->name] as $k=>$v)
				{
					if( !$this->validators[$control->name][$k]->Validate() ) {$res = false;$this->validators[$control->name]=$this->validators[$control->name][$k];break;}
				}
			}
		}
		if(!$res) $GLOBALS["validation"]="failed";
		return $res;
	}
	
	
	
	
	
	// create conditions for the fileds
	function AddCondition($condition)
	{
		$this->conditions[$condition->field] = $condition;
	}
	
	
	
	// process form's conditions
	function EvaluateConditions()
	{
		foreach($this->controls as $k=>$control)
		{
			if($this->conditions[$control->name])
			{
				// set value for the condition-object
				$this->conditions[$control->name]->value = $control->value;
				// check it's rules
				$show_list=Array();
				$hide_list=Array();
				
				if(is_array($this->conditions[$control->name]->rules))
				foreach($this->conditions[$control->name]->rules as $rule)
				{
					if($this->conditions[$control->name]->value==$rule->value)
					{
						$show_list=$rule->show_list;
						$hide_list=$rule->hide_list;
					}
				}
				foreach($show_list as $v) $this->controls[$v]->visible=true;
				foreach($hide_list as $v) $this->controls[$v]->visible=false;
				
				// generation of client-side script
				$script.= "document.getElementById('input_".$control->name."').onchange=onchange_".$this->name."_".$control->name.";\n";
				$script.= "document.getElementById('input_".$control->name."').onclick=onchange_".$this->name."_".$control->name.";\n";
				$script.= "function onchange_".$this->name."_".$control->name."(){\n";
				if(is_array($this->conditions[$control->name]->rules))
				foreach($this->conditions[$control->name]->rules as $rule)
				{
					$script.= "if(document.getElementById('input_".$control->name."').".$this->conditions[$control->name]->jsproperty." == '".$rule->value."'){\n";
					foreach($rule->show_list as $v) 
					$script.= "document.getElementById('".$this->name."_".$v."').style.display='';\n";
					foreach($rule->hide_list as $v) 
					$script.= "document.getElementById('".$this->name."_".$v."').style.display='none';\n";
					$script.= "}\n";
				}
				$script.= "}\n";
			}
		}
		return $script;
	}
}


class FormCondition
{
	var $field;
	var $jsproperty="value";
	var $rules=Array();
	var $value;
	
	function FormCondition($field)
	{
		$this->field=$field;
	}
	
	function CreateRule($value, $show_list, $hide_list)
	{
		if(is_array($value))
		{
			foreach($value as $v)
			$this->rules[] = new FormRule($v, $show_list, $hide_list);
		}
		else
		$this->rules[] = new FormRule($value, $show_list, $hide_list);
	}
}

class FormRule
{
	var $value;
	var $show_list;
	var $hide_list;
	
	function FormRule($value, $show_list, $hide_list)
	{
		$this->value=$value;
		$this->show_list=$show_list;
		$this->hide_list=$hide_list;
	}
}
?>